import logging
import time
import re
import praw
import yaml
from rapidfuzz import fuzz
from string import Template
from praw.exceptions import APIException
from . import models

SUBREDDIT_NAME = 'Piracy'
SETTINGS_FILE = 'settings/WelcomeBot_settings.yaml'
# hours * seconds per hour
# max time for submission existance before the bot rejects approval
MAX_TIME_ALLOWANCE = 8 * 3600

logger = logging.getLogger('main')
console_logger = logging.getLogger('console_logger')

class WelcomeBot:
    def __init__(self, reddit):
        self.reddit = reddit

        self.settings = {}
        self._load_settings()

    def handle_comment(self, comment):
        if comment.subreddit.display_name != SUBREDDIT_NAME:
            return

        user = models.RedditUser.objects.get_or_create(name=comment.author.name)[0]
        if user.has_generally_interacted:
            return

        console_logger.info(f'Welcoming user: {user.name}')
        self._message_user(
            comment.author, self.settings['WELCOME_MESSAGE_SUBJECT_TITLE'], self.settings['WELCOME_MESSAGE'])
        self._update_user(user)

    def handle_submission(self, submission):
        if submission.subreddit.display_name != SUBREDDIT_NAME:
            return

        # refresh post in case automoderator is delayed in applying its own removal rules
        time.sleep(2)
        submission = self.reddit.submission(id=submission.id)

        if (submission.banned_by is not None
                or submission.approved
                or submission.author is None):
            return

        user = models.RedditUser.objects.get_or_create(name=submission.author.name)[0]

        # move forward with removal
        if not user.has_previously_submitted and self.is_link_flair_remove_worthy(submission.link_flair_text):
            logger.info(
                f'Processing submission by new user - {submission.author.name} - https://reddit.com{submission.permalink}')
            self._update_user(user)
            self._remove_submission(submission, user)
        # skip removal of submission and just welcome the user
        elif not user.has_generally_interacted:
            console_logger.info(f'Welcoming user: ${submission.author.name}')
            self._update_user(user, include_submissions=True)
            self._message_user(submission.author,
                               self.settings['WELCOME_MESSAGE_SUBJECT_TITLE'], self.settings['WELCOME_MESSAGE'])

    def _remove_submission(self, submission, user):
        """Attempt to remove submission by new user

        Args:
            submission (praw.models.Submission): praw submission instance
            user (models.RedditUser): database record instance of user
        """
        removal_message = Template(self.settings['REMOVAL_MESSAGE']).safe_substitute(
            REMOVED_POST_PERMALINK=submission.permalink,
            REQUIRED_REPLY=self.settings['REQUIRED_REPLY']
        )
        success = self._message_user(submission.author, self.settings['REMOVAL_MESSAGE_SUBJECT_TITLE'], removal_message)

        if not success:
            self._update_user(user, include_submissions=True)
            return

        submission.mod.remove()

    def handle_message(self, message):
        valid_message, first_message = self._check_message_validity(message)
        if not valid_message:
            return

        logger.info(f' >> Processing agreement message by user: {message.author.name}')

        submission_url = re.search(self.settings['PERMALINK_FIRST_MESSAGE_RE'], first_message.body).group(1)
        submission = self.reddit.submission(url=submission_url)
        self._process_submission_approval(submission, submission_url, message)

    def _check_message_validity(self, message):
        """Checks whether message is not a comment reply and if message is in reply to the bot's original removal message
        and if the user's message contains the required keywords needed to approve their post

        Args:
            message (praw.models.Message): praw message instance

        Returns:
            tuple: tuple of (bool, str); The boolean indicates whether the message is a valid required response
        """
        if not isinstance(message, praw.models.Message):
            return False, ''

        is_agreement_message = False
        if (fuzz.partial_ratio(self.settings['REQUIRED_REPLY'], message.body, score_cutoff=90)
                or re.search(self.settings['REQUIRED_REPLY_RE'], message.body, re.IGNORECASE)):
            is_agreement_message = True

        # check for a previous message in conversation. If it fails, it means the message is a new conversation: ignore message
        try:
            first_message = self.reddit.inbox.message(message.first_message_name[3:])
        except:
            message.mark_read()
            if is_agreement_message:
                message.reply(
                    'There was an issue. You must reply to the original message instead of creating a new conversation')
            return False, ''

        is_reply_to_removal_message = False
        # check if identifier markdown comment is inside first message
        if 'identifier: removal message by WelcomeBot' in first_message.body:
            is_reply_to_removal_message = True

        if not is_reply_to_removal_message:
            return False, ''
        elif not is_agreement_message:
            if any(substr in message.body.lower() for substr in ['wtf', 'fuck']):
                logger.info(f' > Replying badly to {message.author.name}')
                message.reply('Villain, I have done thy mother')
            else:
                logger.info(f' > Replying nicely to {message.author.name}')
                message.reply('I am just a wee bot and you are using strange words. I do not understand')
            return False, ''

        return True, first_message

    def _process_submission_approval(self, submission, submission_url, message):
        """Upon the user replying to the bot for approval, this will help the bot decide
        if the submission if eligible for approval. eg. if the user replied within a set timeframe,
        or if another moderator has decided to remove the post (decline approval)

        Args:
            submission (praw.models.Submission): praw submission instance
            submission_url (str): full URL of submission
            message (praw.models.Message): praw message instance
        """
        # if message is deleted
        if submission.author is None:
            return

        link_flair_text = submission.link_flair_text
        if link_flair_text is None:
            link_flair_text = ''

        # if submission is approved already
        if submission.approved or submission.banned_by is None:
            message.reply(f'[Your submission]({submission_url}) is already visible to everyone.')
        # if reply is past the MAX_TIME_ALLOWANCE (seconds)
        elif time.time() - submission.created_utc > MAX_TIME_ALLOWANCE:
            SORRY_REPLY = Template(self.settings['SORRY_REPLY']).safe_substitute(
                MAX_TIME_ALLOWANCE=MAX_TIME_ALLOWANCE//3600)
            message.reply(SORRY_REPLY)
        # if someone else removed the post
        elif submission.banned_by is not None and submission.banned_by != self.reddit.user.me().name:
            message.reply(self.settings['OVERRIDE_UNAVAILABLE_REPLY'])
        # if post qualifies for bot approval
        elif link_flair_text.startswith(('custom removal', 'rule')):
            message.reply(
                'Hello there. Your submission has been removed for spam/breaking the rules by another moderator. This bot cannot override their actions.')
        elif submission.banned_by == self.reddit.user.me().name:
            logger.info(
                f' >>> Approving submission by {submission.author.name}: https://reddit.com{submission.permalink}')
            submission.mod.approve()
            if submission.is_self:
                self._report_if_possible_rule3(submission)
            message.reply(
                Template(self.settings['ALLS_GOOD_REPLY']).safe_substitute(REMOVED_POST_PERMALINK=submission.permalink)
            )

        user = models.RedditUser.objects.get_or_create(name=submission.author.name)[0]
        self._update_user(user, include_submissions=True)

    def _report_if_possible_rule3(self, submission):
        """Sometimes the submission may be blatantly a rule-breaking post regardless of whether the user is told what the rules are
        this will help the bot to decide whether to report the submission after approval based on keywords for possible rule 3 breaking

        Args:
            submission (praw.models.Submission): praw submission instance
        """

        # reddit report reasons have a max char length of 95-100 i forgot
        max_length = 65
        for str_re in self.settings['POSSIBLE_RULE3_RE']:
            match = re.search(str_re, submission.title, re.IGNORECASE | re.DOTALL)
            if match:
                report_match = match.group()[:max_length]
                submission.report(f'Possible rule 3? - in_title: [{report_match}]')
                return
            match = re.search(str_re, submission.selftext, re.IGNORECASE | re.DOTALL)
            if match:
                report_match = match.group()[:max_length]
                submission.report(f'Possible rule 3? - in_body: [{report_match}]')
                return

    def is_link_flair_remove_worthy(self, link_flair_text) -> bool:
        if link_flair_text is None:
            link_flair_text = ''

        for flair_re in self.settings['FLAIR_TYPES_RE']:
            if re.search(flair_re, link_flair_text, re.IGNORECASE):
                return True
        return False

    @staticmethod
    def _message_user(user, subject, message) -> bool:
        """Sends a user a private message

        Args:
            user (praw.models.Redditor): praw redditor instance
            subject (str): subject line
            message (str): message body

        Returns:
            bool: A boolean indicating if the message was sent successfully
        """
        try:
            user.message(subject=subject, message=message)
            return True
        except APIException as e:
            if e.error_type == "NOT_WHITELISTED_BY_USER_MESSAGE":
                logger.info(f"User {user.name} has a whitelist, therefore there is no way to message them")
            else:
                logger.info(f"Error with attempt to send message: {e}")
            logger.exception(e)
            return False

    def _update_user(self, user: models.RedditUser, include_submissions=False):
        """update RedditUser record in database

        Args:
            user (models.RedditUser): database record instance of user
            include_submissions (bool, optional): Defaults to False.
        """
        user.has_generally_interacted = True
        if include_submissions:
            user.has_previously_submitted = True
        user.save()

    def _load_settings(self):
        with open(SETTINGS_FILE, 'r', encoding='utf8') as fd:
            self.settings = yaml.safe_load(fd)
